/**
 * \file des.cpp
 * \brief Main sourcefile and base process and device definitions
 * \author Wouter Caarls <w.caarls@tudelft.nl> 
 *
 * \verbatim
 * Copyright (c) 2005-7 Wouter Caarls, Delft University of Technology
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * \endverbatim
 */

#include <ucontext.h>
#include <iostream>
#include "des.hpp"
#include "_des.hpp"

DESeventManager g_eventManager;

/* ************************************************************************ */

DESevent::DESevent(DESeventType type, TIME time,
	   class DESsignal *signal, class DESprocess *process) : 
    m_type(type), m_signal(signal), m_process(process)
{
  static unsigned long long int order=0;

  m_time = g_eventManager.m_time + time;
  m_order = order;

  order++;
}

bool operator==(const DESevent& a, const DESevent& b)
{
  return (a.m_type == b.m_type) && (a.m_time == b.m_time) &&
    (a.m_signal == b.m_signal) && (a.m_process == b.m_process) &&
    (a.m_order == b.m_order);
}

bool operator<(const DESevent& a, const DESevent& b)
{
  return (a.m_time < b.m_time) || (a.m_time == b.m_time && a.m_order < b.m_order);
}

bool operator>(const DESevent& a, const DESevent& b)
{
  return (a.m_time > b.m_time) || (a.m_time == b.m_time && a.m_order > b.m_order);
}

std::ostream& operator<<(std::ostream &s, DESevent e)
{
  return s << "Event {m_type=" << e.m_type << ", m_time=" << e.m_time
	   << ", m_signal=" << e.m_signal << ", m_process=" << e.m_process->m_device->m_name
	   << "." << e.m_process->m_name << "}";
}

std::ostream& operator<<(std::ostream &s, DESevent::DESeventType t)
{
  switch (t)
  {
    case DESevent::EventNone:
      return s << "EventNone";
    case DESevent::EventStart:
      return s << "EventStart";
    case DESevent::EventSignal:
      return s << "EventSignal";
    case DESevent::EventAwake:
      return s << "EventAwake";
    case DESevent::EventSchedule:
      return s << "EventSchedule";
  }

  return s;
}

/* ************************************************************************ */

void DESprocess_main(DESprocess *process)
{
  if (process->m_lastEvent.m_type != DESevent::EventStart)
  {
    std::cerr << "Main started with a wrong signal" << std::endl;
  }

  process->main(process->m_argument);

  process->m_device->exit(process);
}

DESprocess::DESprocess(std::string name, void *argument) :
  m_argument(argument), m_name(name), m_facility(NULL)
{
  if (getcontext(&m_context))
  {
     std::cerr << "Couldn't get context" << std::endl;
  }

  m_context.uc_link = 0;
  m_context.uc_stack.ss_sp = malloc( FIBER_STACK );
  m_context.uc_stack.ss_size = FIBER_STACK;
  m_context.uc_stack.ss_flags = 0;     

  if (!m_context.uc_stack.ss_sp)
  {
    std::cerr << "Couldn't allocate stack" << std::endl;
  }

  makecontext(&m_context, (void (*)(void)) &DESprocess_main, 1, this);
}

DESprocess::~DESprocess()
{
  free(m_context.uc_stack.ss_sp);
}

void DESprocess::switchToProcessContext(DESevent event)
{
  g_eventManager.m_process = this;
  m_lastEvent = event;

  //std::cout << "Switching to process " << this << std::endl;

  if (swapcontext(&g_eventManager.m_context, &m_context))
  {
    std::cerr << "Couldn't swap to process context" << std::endl;       
  }
}

void DESprocess::switchToMainContext()
{
  g_eventManager.m_process = NULL;

  //std::cout << "Switching out of process " << this << std::endl;

  if (swapcontext(&m_context, &g_eventManager.m_context))
  {
    std::cerr << "Couldn't swap to main context" << std::endl;  
  }
}

DESevent DESprocess::wait(class DESsignal* signal)
{
  if (!m_device)
  {
    std::cerr << "Cannot wait as process is not bound to a device" << std::endl;
    return DESevent(DESevent::EventNone, 0, NULL, this);
  }
  else
    return m_device->wait(this, signal);
}

DESevent DESprocess::wait(TIME time)
{
  if (!m_device)
  {
    std::cerr << "Cannot wait as process is not bound to a device" << std::endl;
    return DESevent(DESevent::EventNone, 0, NULL, this);
  }
  else
    return m_device->wait(this, time);
}

void DESprocess::dissipate(double energy)
{
  if (!m_device)
    std::cerr << "Cannot dissipate as process is not bound to a device" << std::endl;
  else
    m_device->dissipate(energy);
}

void DESprocess::exit()
{
  if (!m_device)
    std::cerr << "Cannot exit as process is not bound to a device" << std::endl;
  else
    m_device->exit(this);
}

/* ************************************************************************ */

void DESdevice::dissipate(double energy)
{
  m_energy += energy;
}

/* ************************************************************************ */

void DESsignal::signal()
{
  if (!m_listeners.empty())
  {
    DESevent event(DESevent::EventSignal, 0, this, NULL);

    for (LI ii=m_listeners.begin(); ii != m_listeners.end(); ++ii)
    {
      event.m_process = *ii;
      DESaddEvent(event);
    }

    m_listeners.clear();
  }
}

void DESsignal::addListener(DESprocess *process)
{
  m_listeners.push_back(process);
}

/* ************************************************************************ */

TIME DESeventManager::run()
{
  if (getcontext(&m_context))
  {
    std::cerr << "Couldn't get main context" << std::endl;  
  }

  while (m_queue.len() && !m_abort)
  {
    DESevent event = m_queue.pop();

    if (event.m_time < m_time)
    {
      std::cerr << "Time is running backwards" << std::endl;
    }

    if (event.m_time != m_time)
      lt_set_time64(m_trace, event.m_time);

    m_time = event.m_time;

    //std::cout << "processEvent(" << event << ")" << std::endl;
    //m_queue.print();

    m_process = event.m_process;
    event.m_process->m_device->processEvent(event);
    m_events++;
  }

  if (m_abort)
    std::cerr << "Simulation interrupted by user request" << std::endl;

  return m_time;
}

void DESeventManager::addEvent(DESevent event)
{
  //std::cout << "addEvent(" << event << ")" << std::endl;

  m_queue.insert(event);
  //m_queue.print();
}

/* ************************************************************************ */

void DESabort(int sig)
{
  std::cerr << "MOO!" << std::endl;

  g_eventManager.m_abort = 1;
  signal(SIGINT, g_eventManager.m_intsig);
}

TIME DESrun()
{
  TIME ret;

  g_eventManager.m_intsig = signal(SIGINT, DESabort);

  ret = g_eventManager.run();

  signal(SIGINT, g_eventManager.m_intsig);

  return ret;
}

void DESaddEvent(DESevent event)
{
  g_eventManager.addEvent(event);
}

TIME DESgetCurrentTime()
{
  return g_eventManager.m_time;
}

DESprocess *DESgetCurrentProcess()
{
  return g_eventManager.m_process;
}

void DESopenTrace(char *file)
{
  if (!g_eventManager.m_trace)
  {
    g_eventManager.m_trace = lt_init(file);
    lt_set_timescale(g_eventManager.m_trace, -9);
    lt_set_initial_value(g_eventManager.m_trace, '0');
    lt_set_time64(g_eventManager.m_trace, 0);
  }
}

struct lt_trace *DESgetTrace()
{
  return g_eventManager.m_trace;
}

void DEScloseTrace(void)
{
  if (g_eventManager.m_trace)
  {
    lt_close(g_eventManager.m_trace);
    g_eventManager.m_trace = NULL;
  }
}

int DESgetEvents(void)
{
  return g_eventManager.m_events;
}
